package cn.xnatural.jpa;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.query.Query;
import org.hibernate.service.UnknownUnwrapTypeException;
import org.hibernate.transform.Transformers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 对应一个数据源
 */
public class Repo implements AutoCloseable {
    protected static final Logger                         log      = LoggerFactory.getLogger(Repo.class);
    /**
     * 属性集: 包含 datasource, hibernate 两部分
     */
    protected final        Map<String, Object>            attrs;
    /**
     * {@link SessionFactory}
     */
    protected              SessionFactory                 sf;
    /**
     * 当前数据源
     */
    protected              DataSource                     datasource;
    /**
     * 关联哪些实体
     */
    protected final        List<Class<?>> annotatedClass = new LinkedList<>();


    public Repo() { this((Map<String, Object>) null); }

    /**
     * 连接url
     * @param jdbcUrl jdbc url
     */
    public Repo(String jdbcUrl) {
        this(Stream.of(Collections.singletonMap("jdbcUrl", jdbcUrl))
                .collect(Collectors.toConcurrentMap(m -> m.keySet().iterator().next(), m -> m.values().iterator().next())));
    }

    /**
     * 指定jdbcUrl创建Repo
     * @param jdbcUrl jdbc连接串
     * @param username 用户名
     * @param password 密码
     * @param minIdle 最小空闲连接
     * @param maxActive 最大活动连接
     */
    public Repo(String jdbcUrl, String username, String password, Integer minIdle, Integer maxActive) {
        this(Stream.of(Collections.singletonMap("jdbcUrl", jdbcUrl),
                Collections.singletonMap("minimumIdle", minIdle), Collections.singletonMap("maximumPoolSize", maxActive),
                Collections.singletonMap("username", username), Collections.singletonMap("password", password)
        ).collect(Collectors.toConcurrentMap(m -> m.keySet().iterator().next(), m -> m.values().iterator().next())));
    }

    /**
     * {@link #Repo(String, String, String, Integer, Integer)}
     * @param jdbcUrl jdbc连接串
     * @param username 用户名
     * @param password 密码
     */
    public Repo(String jdbcUrl, String username, String password) { this(jdbcUrl, username, password, 1, 8); }


    /**
     * 根据属性集创建Repo
     * @param attrs 属性集
     */
    public Repo(Map<String, Object> attrs) {
        this.attrs = attrs == null ? new ConcurrentHashMap<>() : attrs;
    }


    /**
     * 初始化
     * @return 当前 {@link Repo}
     */
    public Repo init() {
        if (sf != null) throw new RuntimeException("Already inited");

        //1. 数据源
        if (datasource != null) throw new RuntimeException("DataSource already exist");
        datasource = createDataSource(attrs);
        sf = createSessionFactory(attrs, datasource, annotatedClass);
        return this;
    }


    /**
     * 关闭Repo
     */
    @Override
    public void close() {
        try {
            sf.close(); sf = null;
            datasource.getClass().getMethod("close").invoke(datasource);
            datasource = null;
        } catch (Exception ignored) {}
    }


    /**
     * 设置 属性
     * @param key 属性key
     * @param value 属性值
     * @return {@link Repo}
     */
    public Repo setAttr(String key, Object value) { attrs.put(key, value); return this; }


    /**
     * 获取属性
     * @param key 属性key
     * @return 属性值
     */
    public Object getAttr(String key) { return attrs.get(key); }


    /**
     * 添加被管理的实体类
     * @param clzs 实体类
     * @return 当前 {@link Repo}
     */
    public Repo entities(Class<?>... clzs) {
        if (sf != null) throw new RuntimeException("Already inited");
        if (clzs == null) return this;
        for (Class<?> clz : clzs) {
            if (annotatedClass.stream().noneMatch(c -> c.equals(clz))) {
                annotatedClass.add(clz);
            }
        }
        return this;
    }


    // 事务的线程标记
    protected static final ThreadLocal<Boolean> txFlag = ThreadLocal.withInitial(() -> false);
    /**
     * 事务执行方法
     * @param fn 数据库操作函数
     * @param okFn 执行成功后回调
     * @param failFn 执行失败后回调
     */
    public <T> T trans(Function<Session, T> fn, Runnable okFn, Consumer<Throwable> failFn) {
        if (sf == null) throw new RuntimeException("Please init first");
        Session session = sf.getCurrentSession();
        // 当前线程存在事务
        if (txFlag.get()) return fn.apply(session);
        else { // 当前线程没有事务,开启新事务
            Transaction tx = session.getTransaction();
            tx.begin(); txFlag.set(true);
            Throwable ex = null;
            try {
                T r = fn.apply(session); tx.commit(); txFlag.set(false); session.close();
                return r;
            } catch (Throwable t) {
                tx.rollback(); txFlag.set(false); ex = t; session.close();
                if (failFn == null) throw t;
            } finally {
                if (ex != null) {
                    if (failFn != null) failFn.accept(ex);
                } else {// 成功
                    if (okFn != null) okFn.run();
                }
            }
        }
        return null;
    }


    /**
     * {@link #trans(Function, Runnable, Consumer)}
     * @param fn 数据库操作函数. 事务
     */
    public <T> T trans(Function<Session, T> fn) { return trans(fn, null, null); }


    /**
     * 在session中处理
     * @param fn 会话操作函数
     */
    public <T> T doSession(Function<Session, T> fn) {
        if (txFlag.get()) return fn.apply(sf.getCurrentSession());
        else {
            try (Session se = sf.openSession()) {
                return fn.apply(se);
            }
        }
    }


    /**
     * 根据实体类, 查表名字
     * @param eType 实体Class
     * @return 表名
     */
    public <E extends IEntity> String tbName(Class<E> eType) {
        if (sf == null) throw new RuntimeException("Please init first");
        return ((AbstractEntityPersister) ((MappingMetamodel) sf.getMetamodel()).locateEntityDescriptor(eType)).getRootTableName().replace("`", "");
    }


    /**
     * 连接mysql当前数据库的库名
     * @return 数据库名
     */
    public String getDbName() {
        if (sf == null) throw new RuntimeException("Please init first");
        return ((SessionFactoryImpl) sf).getJdbcServices().getJdbcEnvironment().getCurrentCatalog().getText();
    }


    /**
     * 连接的数据库Dialect
     * @return Dialect
     */
    public Dialect getDialect() {
        if (sf == null) throw new RuntimeException("Please init first");
        return ((SessionFactoryImpl) sf).getJdbcServices().getDialect();
    }


    /**
     * 连接的数据库版本
     * @return 版本
     */
    public DatabaseVersion getDBVersion() {
        if (sf == null) throw new RuntimeException("Please init first");
        return getDialect().getVersion();
    }


    /**
     * 连接 jdbcUrl
     * @return 连接 jdbcUrl
     */
    public String getJdbcUrl() {
        if (sf == null) throw new RuntimeException("Please init first");
        try {
            for (PropertyDescriptor pd : Introspector.getBeanInfo(datasource.getClass()).getPropertyDescriptors()) {
                if (pd.getName().equals("jdbcUrl")) return (String) pd.getReadMethod().invoke(datasource);
                if (pd.getName().equals("url")) return (String) pd.getReadMethod().invoke(datasource);
            }
        } catch (Exception e) {
            log.error("", e);
        }
        return null;
    }


    /**
     * <pre>
     * 新增/更新实体
     * NOTE: 根据id判断为[更新/新增]
     * </pre>
     * @param e 实体
     * @return 实体
     */
    public <E extends IEntity> E saveOrUpdate(E e) {
        if (e == null) throw new IllegalArgumentException("Param e required");
        Date d = new Date();
        if (e instanceof ECreatable c && c.getCreateTime() == null) {
            c.setCreateTime(d);
        }
        if (e instanceof EUpdatable u) {
            u.setUpdateTime(d);
        }
        if (e instanceof EVersioning v) {
            v.setVersion(v.getVersion() == null ? 1 : v.getVersion() + 1);
        }
        return trans(se -> {
            if (e.getId() == null) se.persist(e); // persist会回填id, merge不会回填
            else se.merge(e);
            if (e instanceof EVersioning v) { // 自动保存历史数据
                OpHistory op = new OpHistory(e.getClass().getSimpleName(), Objects.toString(e.getId(), null), v.getVersion());
                op.setOperator(e instanceof EUpdater u? u.getUpdater() == null ? (e instanceof ECreator c? c.getCreator() : null) : u.getUpdater() : null);
                op.setCreateTime(d);
                op.setContent(v.content());
                se.persist(op);
            }
            return e;
        });
    }


    /**
     * 根据id查找实体
     * @param eType 实体类型
     * @param id id
     * @return 实体
     */
    public <E extends IEntity> E byId(Class<E> eType, Serializable id) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        return doSession(se -> se.get(eType, id));
    }


    /**
     * 根据某个属性查找实体
     * @param eType 实体类型
     * @param attrName 属性名
     * @param attrValue 属性值, 如果为 null: sql 查询 is null
     * @return 实体
     */
    public <E extends IEntity> E byAttr(Class<E> eType, String attrName, Object attrValue) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        if (attrName == null || attrName.isEmpty()) throw new IllegalArgumentException("Param attrName required");
        return row(eType, (root, query, cb) -> {
            if (attrValue == null) return cb.isNull(root.get(attrName));
            else return cb.equal(root.get(attrName), attrValue);
        });
    }

    /**
     * 根据某两个属性查找实体
     * @param eType 实体类型
     * @param attrName1 属性名
     * @param attrValue1 属性值, 如果为 null: sql 查询 is null
     * @param attrName2 属性名
     * @param attrValue2 属性值, 如果为 null: sql 查询 is null
     * @return 实体
     */
    public <E extends IEntity> E byAttr(Class<E> eType, String attrName1, Object attrValue1, String attrName2, Object attrValue2) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        if (attrName1 == null || attrName1.isEmpty()) throw new IllegalArgumentException("Param attrName1 required");
        if (attrName2 == null || attrName2.isEmpty()) throw new IllegalArgumentException("Param attrName2 required");
        return row(eType, (root, query, cb) -> cb.and(
                attrValue1 == null ? cb.isNull(root.get(attrName1)) : cb.equal(root.get(attrName1), attrValue1),
                attrValue2 == null ? cb.isNull(root.get(attrName2)) : cb.equal(root.get(attrName2), attrValue2)
        ));
    }


    /**
     * 查询一个实体
     * @param eType 实体类型
     * @param spec 条件
     * @return 实体
     */
    public <E extends IEntity> E row(Class<E> eType, CriteriaSpec<E, E> spec) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        return doSession(session -> {
            CriteriaBuilder cb = session.getCriteriaBuilder();
            CriteriaQuery<E> query = cb.createQuery(eType);
            Root<E> root = query.from(eType);
            Object p = spec == null ? null : spec.toPredicate(root, query, cb);
            if (p instanceof Predicate) query.where((Predicate) p);
            return session.createQuery(query).setMaxResults(1).uniqueResult();
        });
    }


    /**
     * 删实体
     * @param e 实体对象
     * @param <E> {@link IEntity}
     */
    public <E extends IEntity> void delete(E e) {
        if (e == null) throw new IllegalArgumentException("Param e required");
        trans(se -> {
            if (e instanceof EDeleting d && e.getId() != null) { // 删除时需要被记录
                OpHistory history = new OpHistory(e.getClass().getSimpleName(), e.getId().toString());
                history.setOperator(e instanceof EUpdater u? u.getUpdater() == null ? (e instanceof ECreator c? c.getCreator() : null) : u.getUpdater() : null);
                Date date = new Date();
                history.setCreateTime(date);
                history.setContent(d.content());
                se.persist(history);
            }
            se.remove(e);
            return null;
        });
    }

    /**
     * <pre>
     * 根据id删除实体
     * </pre>
     * @param eType 实体类型
     * @param id id值
     * @return 删除影响行数
     */
    public <E extends IEntity> int delete(Class<E> eType, Serializable id) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        if (id == null) throw new IllegalArgumentException("Param id required");
        if (EDeleting.class.isAssignableFrom(eType)) {
            delete(byId(eType, id));
            return 1;
        }
        return trans(session -> session.createQuery("delete from " + eType.getSimpleName() + " where id=:id")
                .setParameter("id", id)
                .executeUpdate());
    }


    /**
     * sql update delete insert 执行
     * @param sql sql语句
     * @param params 参数
     * @return 影响条数
     */
    public int execute(String sql, Object...params) {
        if (sql == null || sql.isEmpty()) throw new IllegalArgumentException("Param sql required");
        return trans(session -> fillParam(session.createNativeQuery(sql), params).executeUpdate());
    }


    /**
     * sql 查询一条数据(返回第一条满足条件的数据)
     * @param sql sql 语句
     * @param params 参数
     * @return 一条记录 {@link Map}
     */
    public Map<String, Object> row(String sql, Object...params) { return row(sql, Map.class, params); }

    /**
     * sql 查询 一行数据(返回第一条满足条件的数据)
     * @param sql sql 语句
     * @param wrap 返回结果包装的类型
     * @param params 参数
     * @param <R> 包装类型
     * @return 一条记录 {@link R}
     */
    public <R> R row(String sql, Class<R> wrap, Object...params) {
        if (sql == null || sql.isEmpty()) throw new IllegalArgumentException("Param sql required");
        if (wrap == null) throw new IllegalArgumentException("Param warp required");
        return Map.class.isAssignableFrom(wrap)
                ? (R) doSession(session -> fillParam(session.createNativeQuery(sql).setTupleTransformer(Transformers.ALIAS_TO_ENTITY_MAP), params).setMaxResults(1).uniqueResult())
                : doSession(session -> fillParam(session.createNativeQuery(sql, wrap), params).setMaxResults(1).uniqueResult());
    }


    /**
     * sql 多条查询
     * @param sql sql 语句
     * @param params 参数
     * @return 多条记录 {@link List<Map>}
     */
    public List<Map> rows(String sql, Object...params) { return rows(sql, Map.class, params); }

    /**
     * sql 多条查询
     * @param sql sql
     * @param wrap 返回结果包装的类型
     * @param params sql参数
     * @param <R> 包装类型
     * @return 多条记录 {@link List<R> }
     */
    public <R> List<R> rows(String sql, Class<R> wrap, Object...params) {
        if (sql == null || sql.isEmpty()) throw new IllegalArgumentException("Param sql required");
        if (wrap == null) throw new IllegalArgumentException("Param warp required");
        return Map.class.isAssignableFrom(wrap)
                ? doSession(session -> fillParam(session.createNativeQuery(sql), params).setTupleTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list())
                : doSession(session -> fillParam(session.createNativeQuery(sql, wrap), params).list());
    }


    /**
     * sql 分页查询
     * @param sql sql 语句
     * @param page 第几页 >=1
     * @param limit 每页大小 >=1
     * @param params sql参数
     * @return 一页记录 {@link Page<Map>}
     */
    public Page<Map> paging(String sql, Integer page, Integer limit, Object...params) {
        return paging(sql, page, limit, Map.class, params);
    }

    /**
     * sql 分页查询
     * @param sql sql 语句
     * @param page 第几页 >=1
     * @param limit 每页大小 >=1
     * @param wrap 结果包装类型
     * @param params sql参数
     * @param <T> 包装类型
     * @return 一页记录 {@link Page<T> }
     */
    public <T> Page<T> paging(String sql, Integer page, Integer limit, Class<T> wrap, Object...params) {
        if (sql == null || sql.isEmpty()) throw new IllegalArgumentException("Param sql required");
        if (wrap == null) throw new IllegalArgumentException("Param warp required");
        if (page == null || page < 1) throw new IllegalArgumentException("Param page >=1");
        if (limit == null || limit < 1) throw new IllegalArgumentException("Param limit >=1");
        return doSession(session -> {
            // 当前页数据查询
            Query<T> listQuery = Map.class.isAssignableFrom(wrap)
                    ? fillParam(session.createNativeQuery(sql).setTupleTransformer(Transformers.ALIAS_TO_ENTITY_MAP), params)
                    : fillParam(session.createNativeQuery(sql, wrap), params);
            // 总条数查询
            Query<Long> countQuery = fillParam(session.createNativeQuery("select count(1) from (" + sql + ") t1", Long.class), params);
            return new Page<T>().setPage(page).setPageSize(limit)
                    .setList(listQuery.setFirstResult((page - 1) * limit).setMaxResults(limit).list())
                    .setTotalRow(countQuery.uniqueResult());
        });
    }


    /**
     * sql 参数装配
     * 1. 位置参数 例 ?
     * @param query QueryImplementor
     * @param params sql参数
     */
    protected <T> Query<T> fillParam(Query<T> query, Object[] params) {
        if (params == null || params.length < 1) return query;
        if (query.getParameterMetadata().getNamedParameterNames() != null && query.getParameterMetadata().getNamedParameterNames().size() > 0) { //命名参数sql/hql
            throw new RuntimeException("not support name parameter");
//            for (String pName : query.getParameterMetadata().getNamedParameterNames()) {
//                Object v = params[query.getParameterMetadata().getQueryParameter(pName).getPosition()];
//                if (v == null) continue;
//                if (v instanceof Collection) query.setParameterList(pName, (Collection) v);
//                else if (v.getClass().isArray()) query.setParameterList(pName, (Object[]) v);
//                else query.setParameter(pName, v);
//            }
        } else { //位置参数sql/hql
            for (int i = 0; i < params.length; i++) {
                Object v = params[i];
                if (v == null) continue;
                if (v instanceof Collection) query.setParameterList(i+1, (Collection) v);
                else if (v.getClass().isArray()) query.setParameterList(i+1, (Object[]) v);
                else query.setParameter(i+1, v);
            }
        }
        return query;
    }


    /**
     * 查询全部数据
     * @param eType 实体类型
     * @return 全部数据 {@link List<E>}
     */
    public <E extends IEntity> List<E> all(Class<E> eType) { return rows(eType, null, null, null); }


    /**
     * 查询多条数据
     * @param eType 实体类型
     * @param spec 条件
     * @return 多个实体 {@link List<E>}
     */
    public <E extends IEntity> List<E> rows(Class<E> eType, CriteriaSpec<E, E> spec) {
        return rows(eType, null, null, spec);
    }

    /**
     * 根据某个属性查找实体
     * @param eType 实体类型
     * @param attrName 属性名
     * @param attrValue 属性值, 如果为 null: sql 查询 is null
     * @return 多个实体
     */
    public <E extends IEntity> List<E> rows(Class<E> eType, String attrName, Object attrValue) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        if (attrName == null || attrName.isEmpty()) throw new IllegalArgumentException("Param attrName required");
        return rows(eType, (root, query, cb) -> {
            if (attrValue == null) return cb.isNull(root.get(attrName));
            else return cb.equal(root.get(attrName), attrValue);
        });
    }

    /**
     * 根据某两个属性查找实体
     * @param eType 实体类型
     * @param attrName1 属性名
     * @param attrValue1 属性值, 如果为 null: sql 查询 is null
     * @param attrName2 属性名
     * @param attrValue2 属性值, 如果为 null: sql 查询 is null
     * @return 多个实体
     */
    public <E extends IEntity> List<E> rows(Class<E> eType, String attrName1, Object attrValue1, String attrName2, Object attrValue2) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        if (attrName1 == null || attrName1.isEmpty()) throw new IllegalArgumentException("Param attrName1 required");
        if (attrName2 == null || attrName2.isEmpty()) throw new IllegalArgumentException("Param attrName2 required");
        return rows(eType, (root, query, cb) -> cb.and(
                attrValue1 == null ? cb.isNull(root.get(attrName1)) : cb.equal(root.get(attrName1), attrValue1),
                attrValue2 == null ? cb.isNull(root.get(attrName2)) : cb.equal(root.get(attrName2), attrValue2)
        ));
    }

    /**
     * 查询多条数据
     * @param eType 实体类型
     * @param start 开始行 从0开始
     * @param limit 条数限制
     * @return 多个实体 {@link List<E>}
     */
    public <E extends IEntity> List<E> rows(Class<E> eType, Integer start, Integer limit) {
        return rows(eType, start, limit, null);
    }

    /**
     * 查询多条数据
     * @param eType 实体类型
     * @param start 开始行 从0开始
     * @param limit 条数限制
     * @param spec 条件
     * @return 多个实体 {@link List<E>}
     */
    public <E extends IEntity> List<E> rows(Class<E> eType, Integer start, Integer limit, CriteriaSpec<E, E> spec) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        if (start != null && start < 0) throw new IllegalArgumentException("Param start >= 0 or not give");
        if (limit != null && limit <= 0) throw new IllegalArgumentException("Param limit must > 0 or not give");
        return doSession(session -> {
            CriteriaBuilder cb = session.getCriteriaBuilder();
            CriteriaQuery<E> cQuery = cb.createQuery(eType);
            Root<E> root = cQuery.from(eType);
            Object p = spec == null ? null : spec.toPredicate(root, cQuery, cb);
            if (p instanceof Predicate) cQuery.where((Predicate) p);
            Query<E> query = session.createQuery(cQuery);
            if (start != null) query.setFirstResult(start);
            if (limit != null) query.setMaxResults(limit);
            return query.list();
        });
    }


    /**
     * 分页查询
     * @param eType 实体类型
     * @param page 当前第几页. >=1
     * @param limit 每页大小 >=1
     * @param <E> {@link IEntity}
     * @return 一页实体 {@link Page<E>}
     */
    public <E extends IEntity> Page<E> paging(Class<E> eType, Integer page, Integer limit) {
        return paging(eType, page, limit, null);
    }

    /**
     * 分页查询
     * @param eType 实体类型
     * @param page 当前第几页. >=1
     * @param limit 每页大小 >=1
     * @param listSpec 条件
     * @return 一页实体 {@link Page}
     */
    public <E extends IEntity> Page<E> paging(Class<E> eType, Integer page, Integer limit, CriteriaSpec<E, E> listSpec) {
        return paging(eType, page, limit, listSpec, null);
    }

    /**
     * 分页查询
     * @param eType 实体类型
     * @param page 当前第几页. >=1
     * @param limit 每页大小 >=1
     * @param listSpec 条件
     * @param countSpec 条件
     * @return 一页实体 {@link Page}
     */
    public <E extends IEntity> Page<E> paging(Class<E> eType, Integer page, Integer limit, CriteriaSpec<E, E> listSpec, CriteriaSpec<E, Long> countSpec) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        if (page == null || page < 1) throw new IllegalArgumentException("Param page >=1");
        if (limit == null || limit < 1) throw new IllegalArgumentException("Param limit >=1");
        return trans(session -> {
            CriteriaBuilder cb = session.getCriteriaBuilder();
            CriteriaQuery<E> query = cb.createQuery(eType);
            Root<E> root = query.from(eType);
            Object p = listSpec == null ? null : listSpec.toPredicate(root, query, cb);
            if (p instanceof Predicate) query.where((Predicate) p);
            Page<E> pg = new Page<E>().setPage(page).setPageSize(limit)
                    .setList(session.createQuery(query).setFirstResult((page - 1) * limit).setMaxResults(limit).list());
            if (p instanceof Predicate pp) return pg.setTotalRow(count(eType, pp));
            return pg.setTotalRow(count(eType, countSpec == null ? ((CriteriaSpec) listSpec) : countSpec));
        });
    }


    /**
     * 根据实体类, 统计总数
     * @param eType 实体类型
     * @param <E> {@link IEntity}
     * @return 条数
     */
    public <E extends IEntity> long count(Class<E> eType) { return count(eType, (Predicate) null); }


    /**
     * 根据实体类, 统计
     * @param eType 实体类型
     * @param spec 条件
     * @return 条数
     */
    public <E extends IEntity> long count(Class<E> eType, CriteriaSpec<E, Long> spec) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        return doSession(session -> {
            CriteriaBuilder cb = session.getCriteriaBuilder();
            CriteriaQuery<Long> query = cb.createQuery(Long.class);
            Root<E> root = query.from(eType);
            Object p = spec == null ? null : spec.toPredicate(root, query, cb);
            if (query.isDistinct()) query.select(cb.countDistinct(root));
            else query.select(cb.count(root));
            if (p instanceof Predicate) query.where((Predicate) p);
            query.orderBy(Collections.emptyList()); // 移除排序
            return session.createQuery(query).uniqueResult();
        });
    }

    /**
     * 根据实体类, 统计
     * @param eType 实体类型
     * @param predicate 条件
     * @return 条数
     */
    public <E extends IEntity> long count(Class<E> eType, Predicate predicate) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        return doSession(session -> {
            CriteriaBuilder cb = session.getCriteriaBuilder();
            CriteriaQuery<Long> query = cb.createQuery(Long.class);
            Root<E> root = query.from(eType);
            if (query.isDistinct()) query.select(cb.countDistinct(root));
            else query.select(cb.count(root));
            if (predicate != null) query.where(predicate);
            query.orderBy(Collections.emptyList()); // 移除排序
            return session.createQuery(query).uniqueResult();
        });
    }

    /**
     * 根据实体类, 统计
     * @param eType 实体类型
     * @param attrName 属性名
     * @param attrValue 属性值
     * @return 条数
     */
    public <E extends IEntity> long count(Class<E> eType, String attrName, Object attrValue) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        if (attrName == null || attrName.isEmpty()) throw new IllegalArgumentException("Param attrName required");
        return doSession(session -> {
            CriteriaBuilder cb = session.getCriteriaBuilder();
            CriteriaQuery<Long> query = cb.createQuery(Long.class);
            Root<E> root = query.from(eType);
            query.select(cb.count(root));
            query.where(attrValue == null ? cb.isNull(root.get(attrName)) : cb.equal(root.get(attrName), attrValue));
            return session.createQuery(query).uniqueResult();
        });
    }

    /**
     * 根据实体类, 统计
     * @param eType 实体类型
     * @param attrName1 属性名1
     * @param attrValue1 属性值1
     * @param attrName2 属性名2
     * @param attrValue2 属性值2
     * @return 条数
     */
    public <E extends IEntity> long count(Class<E> eType, String attrName1, Object attrValue1, String attrName2, Object attrValue2) {
        if (eType == null) throw new IllegalArgumentException("Param eType required");
        if (attrName1 == null || attrName1.isEmpty()) throw new IllegalArgumentException("Param attrName1 required");
        if (attrName2 == null || attrName2.isEmpty()) throw new IllegalArgumentException("Param attrName2 required");
        return doSession(session -> {
            CriteriaBuilder cb = session.getCriteriaBuilder();
            CriteriaQuery<Long> query = cb.createQuery(Long.class);
            Root<E> root = query.from(eType);
            query.select(cb.count(root));
            query.where(cb.and(
                    attrValue1 == null ? cb.isNull(root.get(attrName1)) : cb.equal(root.get(attrName1), attrValue1),
                    attrValue2 == null ? cb.isNull(root.get(attrName2)) : cb.equal(root.get(attrName2), attrValue2)
            ));
            return session.createQuery(query).uniqueResult();
        });
    }


    /**
     * 根据条件查是否存在
     * @param eType 实体类型
     * @param spec 条件
     * @return true: 存在, false: 不存在
     */
    public <E extends IEntity> boolean exist(Class<E> eType, CriteriaSpec<E, Long> spec) {
        return count(eType, spec) > 0;
    }

    /**
     * 表中是否存在数据
     * @param eType 实体类型
     * @return true: 存在, false: 不存在
     */
    public <E extends IEntity> boolean exist(Class<E> eType) {
        return count(eType, (Predicate) null) > 0;
    }

    /**
     * 根据实体类, 统计
     * @param eType 实体类型
     * @param attrName 属性名
     * @param attrValue 属性值
     * @return true: 存在, false: 不存在
     */
    public <E extends IEntity> boolean exist(Class<E> eType, String attrName, Object attrValue) {
        return count(eType, attrName, attrValue) > 0;
    }

    /**
     * 实体是否存在
     * @param eType 实体类型
     * @param attrName1 属性名1
     * @param attrValue1 属性值1
     * @param attrName2 属性名2
     * @param attrValue2 属性值2
     * @return true: 存在, false: 不存在
     */
    public <E extends IEntity> boolean exist(Class<E> eType, String attrName1, Object attrValue1, String attrName2, Object attrValue2) {
        return count(eType, attrName1, attrValue1, attrName2, attrValue2) > 0;
    }


    public SessionFactory getSessionFactory() { return sf; }


    public DataSource getDatasource() { return datasource;}

    public List<Class<?>> entities() { return new LinkedList<>(annotatedClass); }


    /**
     * 创建 SessionFactory
     * @param annotatedClass 有jpa注解的类
     */
    public static SessionFactory createSessionFactory(Map<String, Object> attrs, DataSource datasource, List<Class<?>> annotatedClass) {
        // 初始化Hibernate. 可配置的属性名 AvailableSettings
        Map<String, Object> props = new HashMap<>(attrs);
        // props.putIfAbsent("hibernate.hbm2ddl.auto", "none");
        props.putIfAbsent("hibernate.physical_naming_strategy", PhysicalNaming.class);
        props.putIfAbsent("hibernate.implicit_naming_strategy", ImplicitNaming.class);
        props.putIfAbsent("hibernate.current_session_context_class", "thread"); // 会话和 线程绑定
        // props.putIfAbsent("hibernate.temp.use_jdbc_metadata_defaults", "true"); // 自动探测连接的数据库信息,该用哪个Dialect
        props.putIfAbsent("hibernate.boot.allow_jdbc_metadata_access", "true"); // 自动探测连接的数据库信息,该用哪个Dialect
        MetadataSources ms = new MetadataSources(new StandardServiceRegistryBuilder().addService(ConnectionProvider.class, new ConnectionProvider() {
            @Override
            public Connection getConnection() throws SQLException { return datasource.getConnection(); }
            @Override
            public void closeConnection(Connection conn) throws SQLException { conn.close(); }
            @Override
            public boolean supportsAggressiveRelease() { return true; }
            @Override
            public boolean isUnwrappableAs(Class unwrapType) {
                return ConnectionProvider.class.equals(unwrapType) || DataSource.class.isAssignableFrom(unwrapType);
            }
            @Override
            public <T> T unwrap(Class<T> unwrapType) {
                if (ConnectionProvider.class.equals(unwrapType)) {
                    return (T) this;
                } else if (DataSource.class.isAssignableFrom(unwrapType)) {
                    return (T) datasource;
                } else {
                    throw new UnknownUnwrapTypeException(unwrapType);
                }
            }
        }).applySettings(props).build());
        boolean hasVersioning = false;
        boolean hasDeleting = false;
        for (Class<?> clz : annotatedClass) {
            if (EVersioning.class.isAssignableFrom(clz)) hasVersioning = true;
            if (EDeleting.class.isAssignableFrom(clz)) hasDeleting = true;
            ms.addAnnotatedClass(clz);
        }
        if (hasVersioning || hasDeleting) ms.addAnnotatedClass(OpHistory.class);
        return ms.buildMetadata().buildSessionFactory();
    }


    /**
     * 创建一个 数据源
     * @param dsAttr 连接池属性
     * @return {@link DataSource} 数据源
     */
    public static DataSource createDataSource(Map<String, Object> dsAttr) {
        DataSource ds = null;
        // Hikari 数据源
        try {
            Properties props = new Properties();
            dsAttr.forEach((k, v) -> {
                if (k.startsWith("hibernate")) return;
                if ("url".equals(k)) props.put("jdbcUrl", v);
                else if ("minIdle".equals(k)) props.put("minimumIdle", v);
                else if ("maxActive".equals(k)) props.put("maximumPoolSize", v);
                else props.put(k, v);
            });
            Class<?> cfgClz = Class.forName("com.zaxxer.hikari.HikariConfig");
            ds = (DataSource) Class.forName("com.zaxxer.hikari.HikariDataSource").getConstructor(cfgClz)
                    .newInstance(cfgClz.getConstructor(Properties.class).newInstance(props));
            return ds;
        }
        catch(ClassNotFoundException ignored) {}
        catch(Exception ex) { throw new RuntimeException(ex); }

        // druid 数据源
        try {
            Map<String, Object> props = new HashMap<>();
            dsAttr.forEach((k, v) -> {
                if (k.startsWith("hibernate")) return;
                v = Objects.toString(v, "");
                if ("jdbcUrl".equals(k)) props.put("url", v);
                else if ("minimumIdle".equals(k)) props.put("minIdle", v);
                else if ("maximumPoolSize".equals(k)) props.put("maxActive", v);
                else props.put(k, v);
            });
            // if (!props.containsKey("validationQuery")) props.put("validationQuery", "select 1") // oracle
            if (!props.containsKey("filters")) { // 默认监控慢sql
                props.put("filters", "stat");
            }
            if (!props.containsKey("connectionProperties")) {
                // com.alibaba.druid.filter.stat.StatFilter
                props.put("connectionProperties", "druid.stat.logSlowSql=true;druid.stat.slowSqlMillis=5000");
            }
            // 连接过期时间 druid.timeBetweenEvictionRunsMillis=1800000
            if (!props.containsKey("druid.timeBetweenEvictionRunsMillis")) {
                props.put("druid.timeBetweenEvictionRunsMillis", "1800000");
            }
            ds = (DataSource) Class.forName("com.alibaba.druid.pool.DruidDataSourceFactory").getMethod("createDataSource", Map.class).invoke(null, props);

            Object v = props.get("druid.breakAfterAcquireFailure");
            if (v != null) {
                Method m = ds.getClass().getMethod("setBreakAfterAcquireFailure", boolean.class);
                m.invoke(ds, Boolean.parseBoolean(v.toString()));
            }
            return ds;
        }
        catch(ClassNotFoundException ignored) {}
        catch(Exception ex) { throw new RuntimeException(ex); }

        // dbcp2 数据源
        try {
            Properties props = new Properties();
            dsAttr.forEach((k, v) -> {
                if (k.startsWith("hibernate")) return;
                props.put(k, Objects.toString(v, ""));
            });
            // if (!props.containsKey("validationQuery")) props.put("validationQuery", "select 1");
            ds = (DataSource) Class.forName("org.apache.commons.dbcp2.BasicDataSourceFactory").getMethod("createDataSource", Properties.class).invoke(null, props);
            return ds;
        }
        catch(ClassNotFoundException ignored) {}
        catch(Exception ex) { throw new RuntimeException(ex); }

        throw new RuntimeException("No found DataSource impl class");
    }
}